feat: persist theme across page reloads and fix hydration flash#623
Open
pitah23 wants to merge 2 commits into
Open
feat: persist theme across page reloads and fix hydration flash#623pitah23 wants to merge 2 commits into
pitah23 wants to merge 2 commits into
Conversation
…to ProfileBio - Define MAX_BIO_LENGTH (280) in src/lib/validations/profile.ts as the single source of truth, shared by the Zod schema and all UI components - Update ProfileBio.tsx with a live character counter (currentLength / MAX), warning style (text-destructive) at ≥90% capacity, aria-live announcement, and inline Zod-driven error that blocks Save when the limit is exceeded - Update profile/edit/page.tsx to import MAX_BIO_LENGTH from the shared location, removing the local 500-char constant that mismatched the server - Add accessible label/id to the bio textarea in the edit page - Fix broken import path in profile-edit.test.tsx and add useSearchParams mock - Add profile-bio.test.tsx with 16 tests covering counter updates, 90% warning threshold, submission blocking, successful save, and constant/schema consistency
- Add storageKey="theme" to ThemeProvider so next-themes writes to and reads from the "theme" localStorage key (defaultTheme="system", enableSystem, and suppressHydrationWarning were already in place) - Fix both ThemeToggle components to use resolvedTheme for the toggle decision, which correctly handles "system" mode (previously used the raw theme value, so system+dark-OS would not toggle to light) - Add mounted guard to both ThemeToggle components: SSR renders a stable placeholder button (no Sun/Moon icons, no onClick) so users never see a flash of the wrong icon before client hydration - Wire ThemeSelector to useTheme so its active-mode highlight reflects the persisted next-themes state instead of the stale settings prop; mode selection now calls setTheme (persistence) and onUpdate (app state) together; add same mounted guard for hydration safety Tests (27) in theme-persistence.test.tsx: - ThemeProvider forwards storageKey, defaultTheme, enableSystem correctly - localStorage: selected theme stored under "theme", restored on remount, falls back to system when no preference is present - ThemeToggle (both): light↔dark toggle, system+OS-pref toggle, SSR output verified via renderToStaticMarkup (placeholder, no onclick) - ThemeSelector: active mode from useTheme not settings, setTheme+onUpdate called together, correct highlight for all three modes - Cross-component sync: ThemeToggle and ThemeSelector reflect same state
|
@pitah23 is attempting to deploy a commit to the paul joseph's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@pitah23 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #549
Summary
localStorage: wiresstorageKey="theme"intoThemeProvidersonext-themesreads and writes the user's preference under thethemekey;defaultTheme="system"andenableSystemensure unset preferences fall back to the OS settingThemeTogglecomponents previously used the rawthemevalue ("system") for the toggle decision; they now useresolvedThemeso a user on a dark-OS system correctly toggles to"light"(and vice-versa)ThemeTogglecomponents now render a stable placeholder button (no icon, noonClick) during SSR and before client hydration, preventing a flash of the wrong icon; the real button is swapped in aftermounted = trueThemeSelectorwith persisted state:ThemeSelectorpreviously highlighted the active mode based on the stalesettings.modeprop; it now reads fromuseTheme()so the highlight always matches whatnext-themesactually has stored; mode selection calls bothsetTheme(persistence) andonUpdate(app-local state) togetherChanges
frontend/src/app/[locale]/layout.tsxstorageKey="theme"to<ThemeProvider>frontend/src/components/ThemeToggle.tsxresolvedThemefor toggle; addmountedguardfrontend/src/components/ui/ThemeToggle.tsxfrontend/src/components/settings/ThemeSelector.tsxuseTheme; callsetThemeon selection; addmountedguardfrontend/src/__tests__/theme-persistence.test.tsxTests
All 27 tests pass; the 8 pre-existing failures in unrelated suites are unchanged.
ThemeProvider configuration (4)
storageKey="theme",defaultTheme="system",enableSystem, and all other props toNextThemesProviderunchangedlocalStorage persistence (5)
"theme"keydarktheme is restored after remount (stale settings prop is overridden)lighttheme is restored after remountlocalStorage→ active mode is"system"ThemeToggle — ui/ThemeToggle (4 + flash)
light→dark,dark→lightsystem+ dark OS → toggles tolight;system+ light OS → toggles todarkrenderToStaticMarkupconfirms SSR output is a placeholder witharia-labeland noonclick(no flash)ThemeToggle — components/ThemeToggle (3 + flash)
ThemeSelector (6)
useTheme, not the stalesettings.modepropsetThemeandonUpdatetogether for all three modes (light,dark,system)border-primaryhighlight; others do notCross-component sync (3)
ThemeTogglecomponents read the same theme state and produce consistentsetThemecallsThemeSelectorhighlightsdarkwhileThemeToggletoggling away fromdark— both agree on the persisted valueThemeSelectoris immediately whatThemeTogglewould then readTest plan
localStorage(themekey) and reload — falls back to system preferencethemekey is present with the correct value